float_eq 0.2.0

Explicitly bounded comparison of floating point numbers.
Documentation

float_eq

crate documentation Travis status Coverage Status

Explicitly bounded comparison of floating point numbers.

Comparing floating point values for equality is really hard. To get it right requires careful thought and iteration based on the needs of each specific algorithm's inputs and error margins. This API provides a toolbox of components to make your options clear and your choices explicit to future maintainers.

Background

Given how widely algorithmic requirements can vary, float_eq explores the idea that there are no generally sensible default margins for comparisons. This is in contrast to the approach taken by many existing crates, which often provide default epsilon values in checks or implicitly favour particular algorithms. The author's hope is that by exposing the inherent complexity in a uniform way, programmers will find it easier to develop an intuition for how to write effective comparisons. The trade-off is that each individual comparison requires more iteration time and thought.

And yes, this is yet another crate built on the principles described in that Random ASCII floating point comparison article, which is highly recommended background reading 🙂.

Usage

Add this to your cargo.toml:

[dependencies]
float_eq = "0.2"

and, if you're using the 2015 edition, this to your crate root:

extern crate float_eq;

then, you can import items with use:

use float_eq::{assert_float_eq, float_eq};

Comparisons

This crate provides boolean comparison operations:

assert!(float_eq!(1000.0_f32, 1000.0002, ulps <= 4));

// f32::EPSILON.sqrt()
const ROUNDING_ERROR: f32 = 0.00034526698;
assert!(float_ne!(4.0_f32, 4.1, rel <= ROUNDING_ERROR));

And asserts:

// 1.5 * 2_f32.powi(-12), as per SSE intrinsics documentation
const RECIP_REL_EPSILON: f32 = 0.00036621094; 
let recip = 0.1_f32.recip();
assert_float_eq!(recip, 10.0, rel <= RECIP_REL_EPSILON);

assert_float_ne!(0.0_f32, 0.0001, abs <= 0.00005, ulps <= 4);

Arrays of compatible types are also supported, from size 0 to 32 (inclusive):

assert_float_eq!([1.0000001_f32, 2.0], [1.0, 2.0], ulps <= 1);

See the API documentation for a long form introduction to the different kinds of checks, their uses and limitations. Comparison of new types is supported by implementing the FloatEq trait. Asserts may be supported by implementing the FloatDiff and FloatEqDebug traits as well, which provide additional context when debugging.

Optional Features

This crate can be used without the standard library (#![no_std]) by disabling the default std feature. Use this in Cargo.toml:

[dependencies.float_eq]
version = "0.2"
default-features = false

Other optional features:

  • num — implements FloatEq, FloatEqDebug and FloatDiff for num::Complex where it is instanced with a compatible type.

Related efforts

The approx, float-cmp and almost crates all provide a similar style of general comparison operations, whereas assert_float_eq focuses specifically on assertions. In contrast, efloat takes the approach of tracking the error bounds of values as operations are applied.

Contributing

Constructive feedback, suggestions and contributions welcomed, please open an issue.

Changelog

Release information is available in CHANGELOG.md.

Future plans

  • Investigate whether the epsilon types should be generic parameters as opposed to associated types. It feels like composite types may sometimes want to be compared using a global bound and sometimes on a per-component basis, especially if they are composed of heterogeneous types.

  • #[derive] support for comparison of custom types that are composed of already comparable floating point values.

  • Further support for basic Rust language components like tuples and containers of compatible types like Vec, likely using PartialEq's support as a guide.

  • Investigate the safety guarantees of the ulps check. Currently, it doesn't act like the default floating point checks when it comes to NaNs and other special values.

  • More exhaustive testing. Tests currently cover all basic functionality, but there are lots of edge cases that aren't being tested yet.

  • Benchmark performance, especially the implications of chaining multiple tests.